﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace ChessModel
{
    public class PieceLists
    {
        private List<Piece>[] items;

        internal PieceLists () { Clear (); }

        public List<Piece> this[Player player]
        { get { return items[player.Index]; } }

        public int Count (Player player)
        { return items[player.Index].Count; }

        internal void Add (Piece newPiece)
        { items[newPiece.Player.Index].Add (newPiece); }

        internal void Clear ()
        {
            if (items == null || items.Length != 2)
                items = new List<Piece>[] { new List<Piece> (), new List<Piece> () };
            else
            {
                items[0].Clear ();
                items[1].Clear ();
            }
        }

        // Used by pawn promotion.
        internal void replace (Piece oldPiece, Piece newPiece)
        {
            for (int i = 0; i < Count (oldPiece.Player); ++i)
                if (this[oldPiece.Player][i].X == oldPiece.X && this[oldPiece.Player][i].Y == oldPiece.Y)
                {
                    this[oldPiece.Player][i] = newPiece;
                    break;
                }
        }
    }


    public class Square
    {
        public Square () { }

        static public readonly Square Empty = new Square ();

        public bool IsOccupied { get { return this is Piece; } }

        public virtual bool IsOpponent (Player player) { return false; }
        public virtual bool NotPlayer (Player player) { return true; }
        public virtual bool IsPlayer (Player player) { return false; }

        public override string ToString () { return "."; }
    }


    public abstract class Piece : Square
    {
        public Player Player { get; protected set; }
        public Coordinate Place { get; internal set; }
        public bool IsCaptured { get; internal set; }
        public ChessView Game { get; internal set; }
        internal int firstMoved = -1;

        protected Piece (Player newSide)
        { Game = null; Player = newSide; Place = new Coordinate (-1, -1); }

        protected Piece (Piece oldPiece)
        { Game = oldPiece.Game; Player = oldPiece.Player; Place = oldPiece.Place; }

        public Piece ConstructOppositePiece ()
        { return Factory (Player.Opponent); }

        public abstract char Letter { get; }
        public abstract int Index { get; }
        internal abstract Piece Factory (Player newSide);
        internal abstract IEnumerable moves { get; }

        public int X { get { return Place.X; } }
        public int Y { get { return Place.Y; } }
        public bool NeverMoved { get { return firstMoved < 0; } }

        public override bool IsOpponent (Player player) { return this.Player != player; }
        public override bool NotPlayer (Player player) { return this.Player != player; }
        public override bool IsPlayer (Player player) { return this.Player == player; }

        public override string ToString ()
        {
            char pieceLetter = Letter;
            if (Player == Player.Black)
                pieceLetter = Char.ToLower (pieceLetter);
            return pieceLetter.ToString ();
        }

        public IEnumerable LegalMoves
        {
            get
            {
                if (this.Player == Game.PlayerTurn)
                    foreach (Coordinate place in moves)
                    {
                        Game.Plies.Push (this, place.X, place.Y);
                        bool threatened = Game.isKingThreatened (Player);
                        Game.Plies.Pop ();

                        if (!threatened)
                            yield return place;
                    }
            }
        }
    }


    public class King : Piece
    {
        public King (Player newSide) : base (newSide) { }

        internal override Piece Factory (Player newSide)
        { return new King (newSide); }

        public override char Letter { get { return 'K'; } }
        public override int Index { get { return 1; } }

        internal override IEnumerable moves
        {
            get
            {
                int xMin = X - 1;
                if (xMin < 0) xMin = 0;
                int xMax = X + 1;
                if (xMax >= Game.Board.XSize) xMax = Game.Board.XSize - 1;

                if (Y > 0)
                    for (int x = xMin; x <= xMax; ++x)
                        if (Game.Board[x, Y - 1].NotPlayer (Player))
                            yield return new Coordinate (x, Y - 1);

                if (Y < Game.Board.YSize - 1)
                    for (int x = xMin; x <= xMax; ++x)
                        if (Game.Board[x, Y + 1].NotPlayer (Player))
                            yield return new Coordinate (x, Y + 1);

                bool testedLeft = false; bool testedRight = false;

                if (NeverMoved && Game.Status == GameStatus.Go)
                {
                    Piece rook = Game.Board[0, Y] as Rook;
                    if (rook != null && rook.Player == Player)
                        if (rook.NeverMoved && Game.Board[1, Y] == Square.Empty)
                            if (Game.Board[X - 1, Y] == Square.Empty && Game.Board[X - 2, Y] == Square.Empty)
                            {
                                Game.Plies.Push (this, X - 1, Y);
                                bool threatened = Game.isKingThreatened (Player);
                                Game.Plies.Pop ();
                                if (!threatened)
                                {
                                    testedLeft = true;
                                    yield return new Coordinate (X - 1, Y);
                                    yield return new Coordinate (X - 2, Y);
                                }
                            }

                    rook = Game.Board[Game.Board.XSize - 1, Y] as Rook;
                    if (rook != null && rook.Player == Player)
                        if (rook.NeverMoved)
                            if (Game.Board[X + 1, Y] == Square.Empty && Game.Board[X + 2, Y] == Square.Empty)
                            {
                                Game.Plies.Push (this, X + 1, Y);
                                bool threatened = Game.isKingThreatened (Player);
                                Game.Plies.Pop ();
                                if (!threatened)
                                {
                                    testedRight = true;
                                    yield return new Coordinate (X + 1, Y);
                                    yield return new Coordinate (X + 2, Y);
                                }
                            }
                }

                if (!testedLeft && X > 0 && Game.Board[X - 1, Y].NotPlayer (Player))
                    yield return new Coordinate (X - 1, Y);

                if (!testedRight && X < Game.Board.XSize - 1 && Game.Board[X + 1, Y].NotPlayer (Player))
                    yield return new Coordinate (X + 1, Y);
            }
        }
    }


    public class Queen : Piece
    {
        public Queen (Player newSide) : base (newSide) { }

        internal override Piece Factory (Player newSide)
        { return new Queen (newSide); }

        public override char Letter { get { return 'Q'; } }
        public override int Index { get { return 2; } }

        internal override IEnumerable moves
        {
            get
            {
                foreach (Coordinate move in new Bishop (this).moves)
                    yield return move;

                foreach (Coordinate move in new Rook (this).moves)
                    yield return move;
            }
        }
    }


    public class Rook : Piece
    {
        public Rook (Player newSide) : base (newSide) { }
        internal Rook (Queen queen) : base (queen) { }

        internal override Piece Factory (Player newSide)
        { return new Rook (newSide); }

        public override char Letter { get { return 'R'; } }

        public override int Index { get { return StaticIndex; } }
        public static readonly int StaticIndex = 3;

        internal override IEnumerable moves
        {
            get
            {
                for (int x = X - 1; x >= 0; --x)
                {
                    Square target = Game.Board[x, Y];
                    if (target.NotPlayer (Player))
                        yield return new Coordinate (x, Y);
                    if (target.IsOccupied)
                        break;
                }

                for (int x = X + 1; x < Game.Board.XSize; ++x)
                {
                    Square target = Game.Board[x, Y];
                    if (target.NotPlayer (Player))
                        yield return new Coordinate (x, Y);
                    if (target.IsOccupied)
                        break;
                }

                for (int y = Y - 1; y >= 0; --y)
                {
                    Square target = Game.Board[X, y];
                    if (target.NotPlayer (Player))
                        yield return new Coordinate (X, y);
                    if (target.IsOccupied)
                        break;
                }

                for (int y = Y + 1; y < Game.Board.YSize; ++y)
                {
                    Square target = Game.Board[X, y];
                    if (target.NotPlayer (Player))
                        yield return new Coordinate (X, y);
                    if (target.IsOccupied)
                        break;
                }
            }
        }
    }


    public class Bishop : Piece
    {
        public Bishop (Player newSide) : base (newSide) { }
        internal Bishop (Queen queen) : base (queen) { }

        internal override Piece Factory (Player newSide)
        { return new Bishop (newSide); }

        public override char Letter { get { return 'B'; } }
        public override int Index { get { return 4; } }

        static private readonly int[][] deltas = new int[][]
        {
            new int[] { -1, -1 }, new int[] { -1, 1 },
            new int[] { 1, -1 }, new int[] { 1, 1 }
        };

        internal override IEnumerable moves
        {
            get
            {
                foreach (int[] delta in deltas)
                {
                    for (int x = X, y = Y; ; )
                    {
                        x += delta[0];
                        y += delta[1];

                        if (x < 0 || y < 0 || x >= Game.Board.XSize || y >= Game.Board.YSize)
                            break;

                        Square target = Game.Board[x, y];
                        if (target.NotPlayer (Player))
                            yield return new Coordinate (x, y);
                        if (target.IsOccupied)
                            break;
                    }
                }
            }
        }
    }


    public class Knight : Piece
    {
        public Knight (Player newSide) : base (newSide) { }

        internal override Piece Factory (Player newSide)
        { return new Knight (newSide); }

        public override char Letter { get { return 'N'; } }
        public override int Index { get { return 5; } }

        static private readonly int[][] deltas = new int[][]
        {
            new int[] { -2, -1 }, new int []{ -2, 1 },
            new int[] { -1, -2 }, new int[] { -1, 2 },
            new int[] { 1, -2 }, new int[] { 1, 2 },
            new int[] { 2, -1 }, new int[] { 2, 1 } };

        internal override IEnumerable moves
        {
            get
            {
                foreach (int[] delta in deltas)
                {
                    int x = X - delta[0];
                    int y = Y - delta[1];
                    if (x >= 0 && y >= 0 && x < Game.Board.XSize && y < Game.Board.YSize)
                        if (Game.Board[x, y].NotPlayer (this.Player))
                            yield return new Coordinate (x, y);
                }
            }
        }
    }

    public class Pawn : Piece
    {
        public Pawn (Player newSide) : base (newSide) { }

        internal override Piece Factory (Player newSide)
        { return new Pawn (newSide); }

        public override char Letter { get { return 'P'; } }
        public override int Index { get { return StaticIndex; } }
        public static readonly int StaticIndex = 6;

        private IEnumerable captures (int x, int yDelta, int passantRank)
        {
            Square target = Game.Board[x, Y + yDelta];
            if (target.IsOpponent (Player))
                yield return new Coordinate (x, Y + yDelta);

            if (Y == passantRank)
            {
                Square passing = Game.Board[x, Y];
                if (!target.IsOccupied && passing.IsOpponent (this.Player) && passing is Pawn)
                    if (Game.Plies.Count > 0)
                        if (Game.Plies.Top.From.Y == Y + yDelta + yDelta)
                            if (Game.Plies.Top.From.X == x)
                                yield return new Coordinate (x, Y + yDelta);
            }
        }

        internal override IEnumerable moves
        {
            get
            {
                int yDelta, homeRank, passantRank;

                if (Player == Player.White)
                { yDelta = 1; homeRank = 1; passantRank = Game.Board.YSize - 4; }
                else
                { yDelta = -1; homeRank = Game.Board.YSize - 2; passantRank = 3; }

                int y = Y + yDelta;
                if (Game.Board[X, y] == Square.Empty)
                {
                    yield return new Coordinate (X, y);

                    if (Y == homeRank)
                    {
                        y = y + yDelta;
                        if (Game.Board[X, y] == Square.Empty)
                            yield return new Coordinate (X, y);
                    }
                }

                if (X > 0)
                    foreach (Coordinate cap in captures (X - 1, yDelta, passantRank))
                        yield return cap;

                if (X < Game.Board.XSize - 1)
                    foreach (Coordinate cap in captures (X + 1, yDelta, passantRank))
                        yield return cap;
            }
        }
    }
}
